///<reference path='troop-types.ts' />
///<reference path='utilities.ts' />
///<reference path='exceptions.ts' />

class ServiceLocator {
    private _diaymoArmyCreate: (name: string) => IDiamyoArmy;
    private _territoryArmyCreate: () => ITerritoryArmy;

    RegisterDiaymoArmy(createFunc: (name: string) => IDiamyoArmy) {
        this._diaymoArmyCreate = createFunc;
    }

    GetDiaymoArmy(name: string) {
        return this._diaymoArmyCreate(name);
    }

    RegisterTerritoryArmy(createFunc:() => ITerritoryArmy) {
        this._territoryArmyCreate = createFunc;
    }

    GetTerritoryArmy() {
        return this._territoryArmyCreate();
    }
}

interface IArmy {
    AddTroop(troop: Troop): void;

    ClearRoninTroops(): void;

    RemoveTroop(troop: Troop): void;

    RemoveTroopByType(troopType: TroopTypes): void;

    getCanRemoveTroopByType(troopType: TroopTypes): bool;

    getRoninSlotsAvailable(): bool;

    getCanRemoveShogunTroop(): bool;

    getShogunSlotsAvailable(): bool;

    getAllTroops(): Troop[];
}

class Army implements IArmy {
    private _maximumShogunTroops: number;

    private _shogunTroops: Troop[];
    private _roninTroops: Troop[];

    constructor (maximumShogunTroops: number) {
        this._maximumShogunTroops = maximumShogunTroops;
        this._shogunTroops = [];
        this._roninTroops = [];
    }

    public AddTroop(troop: Troop) {
        if (troop.TroopType == TroopTypes.Ronin) {
            if(!this.getRoninSlotsAvailable())
                throw new RangeException();
            this._roninTroops.push(troop);
        }
        else {
            if(!this.getShogunSlotsAvailable())
                throw new RangeException();
            this._shogunTroops.push(troop);            
        }
    }

    public RemoveTroop(troop: Troop) {
        this.RemoveTroopByType(troop.TroopType);
    }

    public RemoveTroopByType(troopType: TroopTypes) {
        if(!this.getCanRemoveTroopByType(troopType))
            throw new TroopConstraintViolationException("You must always have at least one more shogun troop in your army than Ronin troops.");            
        if (troopType == TroopTypes.Ronin) {
            this._roninTroops.pop();
        }
        else {
            Utilities.removeFirst(<Array><any>this._shogunTroops, (candidate) => troopType == candidate.TroopType);
        }
    }

    public getCanRemoveTroopByType(troopType: TroopTypes) {
        var troopCount = this.getTroopCountByType(troopType);
        if(troopCount == 0)
            return false;
        else if(troopType != TroopTypes.Ronin)
            return this.getCanRemoveShogunTroop();
        return true;
    }

    private getTroopCountByType(troopType: TroopTypes) {
        if(troopType == TroopTypes.Ronin)
            return this._roninTroops.length;
        else
            return Utilities.count(<Array><any>this._shogunTroops, (candidate) => candidate.TroopType == troopType);
    }

    public ClearRoninTroops() {
        this._roninTroops = [];
    }

    public getRoninSlotsAvailable(): bool { return this._roninTroops.length < this._shogunTroops.length - 1; }

    public getCanRemoveShogunTroop(){
        if (this._roninTroops.length == 0) {
            return this._shogunTroops.length > 0;
        }
        else {
            var shogunTroopLead = this._shogunTroops.length - this._roninTroops.length;
            return shogunTroopLead > 1;
        }
    }

    public getShogunSlotsAvailable(): bool { return this._shogunTroops.length < this._maximumShogunTroops; }

    public getAllTroops() {
        var allTroops: Troop[] = [];
        for (var index: number = 0; index < this._shogunTroops.length; ++index) 
            allTroops.push(this._shogunTroops[index]);
        for (var index: number = 0; index < this._roninTroops.length; ++index)
            allTroops.push(this._roninTroops[index]);
        return allTroops;
    }

    private get MaximumRoninTroops() { return this._maximumShogunTroops - 1; }
}

interface ITerritoryArmy extends IArmy {
}

class TerritoryArmy extends Army implements ITerritoryArmy {
    static MaxTerritoryArmySize: number = 5;
    constructor () {
        super(TerritoryArmy.MaxTerritoryArmySize);
    }
}

interface IDiamyoArmy extends IArmy {
    
    getSamuraiSlotAvailable(): bool;

    getAshiguruSlotAvailable(): bool;

    getName(): string;
}

class DiamyoArmy extends Army implements IDiamyoArmy {
    static MaxDiaymoArmySize: number = 16;
    static MaxSamuariSlots: number = 8;
    static MaxAshiguruSlots: number = 8;

    private _name: string;
    private _samuraiWarriorCount: number;
    private _ashiguruWarriorCount: number;

    constructor (name:string) {
        super(DiamyoArmy.MaxDiaymoArmySize);
        this._name = name;
        this._samuraiWarriorCount = 0;
        this._ashiguruWarriorCount = 0;
    }

    AddTroop(troop: Troop) {
        if (troop.TroopType == TroopTypes.Ronin) {
            super.AddTroop(troop);
        }
        else {
            if (troop.IsSamurai) {
                if (!this.getSamuraiSlotAvailable())
                    throw new RangeException();
                ++this._samuraiWarriorCount;
                super.AddTroop(troop);
            }
            else {
                if(!this.getAshiguruSlotAvailable())
                    throw new RangeException();
                ++this._ashiguruWarriorCount;
                super.AddTroop(troop);
            }
        }
    }

    RemoveTroop(troop: Troop) {
        super.RemoveTroop(troop);
        if(troop.IsSamurai)
            --this._samuraiWarriorCount;
        else
            --this._ashiguruWarriorCount;
    }

    getSamuraiSlotAvailable(): bool { return this._samuraiWarriorCount < DiamyoArmy.MaxSamuariSlots && this.getShogunSlotsAvailable(); }

    getAshiguruSlotAvailable(): bool { return this._ashiguruWarriorCount < DiamyoArmy.MaxAshiguruSlots && this.getShogunSlotsAvailable(); }

    getName() { return this._name; }
}